/* -------------------------------------------------------------------------
 *
 * contrib/sepgsql/dml.c
 *
 * 处理 DML 权限检查的例程
 *
 * Copyright (c) 2010-2022, PostgreSQL Global Development Group
 *
 * -------------------------------------------------------------------------
 */
#include "postgres.h"

#include "access/htup_details.h"
#include "access/sysattr.h"
#include "access/tupdesc.h"
#include "catalog/catalog.h"
#include "catalog/dependency.h"
#include "catalog/heap.h"
#include "catalog/pg_attribute.h"
#include "catalog/pg_class.h"
#include "catalog/pg_inherits.h"
#include "commands/seclabel.h"
#include "commands/tablecmds.h"
#include "executor/executor.h"
#include "nodes/bitmapset.h"
#include "sepgsql.h"
#include "utils/lsyscache.h"
#include "utils/syscache.h"

/*
 * fixup_whole_row_references
 *
 * 当用户引用整个行的 Var 时，等同于引用
 * 所有用户列（而不是系统列）。因此，我们需要修正给定的
 * bitmapset，如果它包含整个行的引用。
 */
static Bitmapset * fc_fixup_whole_row_references(Oid fc_relOid, Bitmapset *fc_columns)
{
	Bitmapset  *fc_result;
	HeapTuple	fc_tuple;
	AttrNumber	fc_natts;
	AttrNumber	fc_attno;
	int			fc_index;

	/* 如果没有整个行的引用，则无事可做 */
	fc_index = InvalidAttrNumber - FirstLowInvalidHeapAttributeNumber;
	if (!bms_is_member(fc_index, fc_columns))
		return fc_columns;

	/* 获取属性数量 */
	fc_tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(fc_relOid));
	if (!HeapTupleIsValid(fc_tuple))
		elog(ERROR, "cache lookup failed for relation %u", fc_relOid);
	fc_natts = ((Form_pg_class) GETSTRUCT(fc_tuple))->relnatts;
	ReleaseSysCache(fc_tuple);

	/* 从列集移除位 0，将所有未删除的列添加进来 */
	fc_result = bms_copy(fc_columns);
	fc_result = bms_del_member(fc_result, fc_index);

	for (fc_attno = 1; fc_attno <= fc_natts; fc_attno++)
	{
		fc_tuple = SearchSysCache2(ATTNUM,
								ObjectIdGetDatum(fc_relOid),
								Int16GetDatum(fc_attno));
		if (!HeapTupleIsValid(fc_tuple))
			continue;			/* 意外情况，是否应该报错？ */

		if (!((Form_pg_attribute) GETSTRUCT(fc_tuple))->attisdropped)
		{
			fc_index = fc_attno - FirstLowInvalidHeapAttributeNumber;
			fc_result = bms_add_member(fc_result, fc_index);
		}

		ReleaseSysCache(fc_tuple);
	}
	return fc_result;
}

/*
 * fixup_inherited_columns
 *
 * 当用户在具有子项的表上查询时，它隐式地访问
 * 子表。因此，我们还需要检查子表和列的安全标签，但这里
 * 并不能保证父表和子表之间的属性数量相同。
 * 它返回一个 bitmapset，包含基于父表给定 bitmapset 的子表
 * 的属性编号。
 */
static Bitmapset * fc_fixup_inherited_columns(Oid fc_parentId, Oid fc_childId, Bitmapset *fc_columns)
{
	Bitmapset  *fc_result = NULL;
	int			fc_index;

	/*
	 * 显然，这里不需要做任何事情
	 */
	if (fc_parentId == fc_childId)
		return fc_columns;

	fc_index = -1;
	while ((fc_index = bms_next_member(fc_columns, fc_index)) >= 0)
	{
		/* 位数由 FirstLowInvalidHeapAttributeNumber 偏移 */
		AttrNumber	fc_attno = fc_index + FirstLowInvalidHeapAttributeNumber;
		char	   *fc_attname;

		/*
		 * 整个行引用将在稍后修复
		 */
		if (fc_attno == InvalidAttrNumber)
		{
			fc_result = bms_add_member(fc_result, fc_index);
			continue;
		}

		fc_attname = get_attname(fc_parentId, fc_attno, false);
		fc_attno = get_attnum(fc_childId, fc_attname);
		if (fc_attno == InvalidAttrNumber)
			elog(ERROR, "cache lookup failed for attribute %s of relation %u",
				 fc_attname, fc_childId);

		fc_result = bms_add_member(fc_result,
								fc_attno - FirstLowInvalidHeapAttributeNumber);

		pfree(fc_attname);
	}

	return fc_result;
}

/*
 * check_relation_privileges
 *
 * 它实际上检查对某个关系及其列所需的权限。
 */
static bool fc_check_relation_privileges(Oid fc_relOid,
						  Bitmapset *fc_selected,
						  Bitmapset *fc_inserted,
						  Bitmapset *fc_updated,
						  uint32 fc_required,
						  bool fc_abort_on_violation)
{
	ObjectAddress fc_object;
	char	   *fc_audit_name;
	Bitmapset  *fc_columns;
	int			fc_index;
	char		fc_relkind = get_rel_relkind(fc_relOid);
	bool		fc_result = true;

	/*
	 * 硬编码政策：SE-PostgreSQL 强制实施 - 客户端无法使用 DML 修改
	 * 系统目录 - 客户端无法使用 DML 引用/修改 toast
	 * 关系
	 */
	if (sepgsql_getenforce() > 0)
	{
		if ((fc_required & (SEPG_DB_TABLE__UPDATE |
						 SEPG_DB_TABLE__INSERT |
						 SEPG_DB_TABLE__DELETE)) != 0 &&
			IsCatalogRelationOid(fc_relOid))
			ereport(ERROR,
					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
					 errmsg("SELinux: hardwired security policy violation")));

		if (fc_relkind == RELKIND_TOASTVALUE)
			ereport(ERROR,
					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
					 errmsg("SELinux: hardwired security policy violation")));
	}

	/*
	 * 检查对关系的权限
	 */
	fc_object.classId = RelationRelationId;
	fc_object.objectId = fc_relOid;
	fc_object.objectSubId = 0;
	fc_audit_name = getObjectIdentity(&fc_object, false);
	switch (fc_relkind)
	{
		case RELKIND_RELATION:
		case RELKIND_PARTITIONED_TABLE:
			fc_result = sepgsql_avc_check_perms(&fc_object,
											 SEPG_CLASS_DB_TABLE,
											 fc_required,
											 fc_audit_name,
											 fc_abort_on_violation);
			break;

		case RELKIND_SEQUENCE:
			Assert((fc_required & ~SEPG_DB_TABLE__SELECT) == 0);

			if (fc_required & SEPG_DB_TABLE__SELECT)
				fc_result = sepgsql_avc_check_perms(&fc_object,
												 SEPG_CLASS_DB_SEQUENCE,
												 SEPG_DB_SEQUENCE__GET_VALUE,
												 fc_audit_name,
												 fc_abort_on_violation);
			break;

		case RELKIND_VIEW:
			fc_result = sepgsql_avc_check_perms(&fc_object,
											 SEPG_CLASS_DB_VIEW,
											 SEPG_DB_VIEW__EXPAND,
											 fc_audit_name,
											 fc_abort_on_violation);
			break;

		default:
			/* 无需检查任何内容 */
			break;
	}
	pfree(fc_audit_name);

	/*
	 * 仅应检查由关系拥有的列
	 */
	if (fc_relkind != RELKIND_RELATION && fc_relkind != RELKIND_PARTITIONED_TABLE)
		return true;

	/*
	 * 检查对列的权限
	 */
	fc_selected = fc_fixup_whole_row_references(fc_relOid, fc_selected);
	fc_inserted = fc_fixup_whole_row_references(fc_relOid, fc_inserted);
	fc_updated = fc_fixup_whole_row_references(fc_relOid, fc_updated);
	fc_columns = bms_union(fc_selected, bms_union(fc_inserted, fc_updated));

	while ((fc_index = bms_first_member(fc_columns)) >= 0)
	{
		AttrNumber	fc_attnum;
		uint32		fc_column_perms = 0;

		if (bms_is_member(fc_index, fc_selected))
			fc_column_perms |= SEPG_DB_COLUMN__SELECT;
		if (bms_is_member(fc_index, fc_inserted))
		{
			if (fc_required & SEPG_DB_TABLE__INSERT)
				fc_column_perms |= SEPG_DB_COLUMN__INSERT;
		}
		if (bms_is_member(fc_index, fc_updated))
		{
			if (fc_required & SEPG_DB_TABLE__UPDATE)
				fc_column_perms |= SEPG_DB_COLUMN__UPDATE;
		}
		if (fc_column_perms == 0)
			continue;

		/* 获取列的权限 */
		fc_attnum = fc_index + FirstLowInvalidHeapAttributeNumber;

		fc_object.classId = RelationRelationId;
		fc_object.objectId = fc_relOid;
		fc_object.objectSubId = fc_attnum;
		fc_audit_name = getObjectDescription(&fc_object, false);

		fc_result = sepgsql_avc_check_perms(&fc_object,
										 SEPG_CLASS_DB_COLUMN,
										 fc_column_perms,
										 fc_audit_name,
										 fc_abort_on_violation);
		pfree(fc_audit_name);

		if (!fc_result)
			return fc_result;
	}
	return true;
}

/*
 * sepgsql_dml_privileges
 *
 * DML 权限检查的入口点
 */
bool sepgsql_dml_privileges(List *fc_rangeTabls, bool fc_abort_on_violation)
{
	ListCell   *fc_lr;

	foreach(fc_lr, fc_rangeTabls)
	{
		RangeTblEntry *fc_rte = lfirst(fc_lr);
		uint32		fc_required = 0;
		List	   *fc_tableIds;
		ListCell   *fc_li;

		/*
		 * 仅应检查常规关系
		 */
		if (fc_rte->rtekind != RTE_RELATION)
			continue;

		/*
		 * 查找所需的权限
		 */
		if (fc_rte->requiredPerms & ACL_SELECT)
			fc_required |= SEPG_DB_TABLE__SELECT;
		if (fc_rte->requiredPerms & ACL_INSERT)
			fc_required |= SEPG_DB_TABLE__INSERT;
		if (fc_rte->requiredPerms & ACL_UPDATE)
		{
			if (!bms_is_empty(fc_rte->updatedCols))
				fc_required |= SEPG_DB_TABLE__UPDATE;
			else
				fc_required |= SEPG_DB_TABLE__LOCK;
		}
		if (fc_rte->requiredPerms & ACL_DELETE)
			fc_required |= SEPG_DB_TABLE__DELETE;

		/*
		 * 如果没有需要检查的内容，则跳过
		 */
		if (fc_required == 0)
			continue;

		/*
		 * 如果这个 RangeTblEntry 也应该引用继承
		 * 表，我们需要检查子表的安全标签。因此，我们
		 * 将 rte->relid 扩展为继承层次结构的 OID 列表，然后
		 * 将为每个关系调用检查例程。
		 */
		if (!fc_rte->inh)
			fc_tableIds = list_make1_oid(fc_rte->relid);
		else
			fc_tableIds = find_all_inheritors(fc_rte->relid, NoLock, NULL);

		foreach(fc_li, fc_tableIds)
		{
			Oid			fc_tableOid = lfirst_oid(fc_li);
			Bitmapset  *fc_selectedCols;
			Bitmapset  *fc_insertedCols;
			Bitmapset  *fc_updatedCols;

			/*
			 * 子表具有不同的属性编号，因此我们需要修复它们。
			 */
			fc_selectedCols = fc_fixup_inherited_columns(fc_rte->relid, fc_tableOid,
												   fc_rte->selectedCols);
			fc_insertedCols = fc_fixup_inherited_columns(fc_rte->relid, fc_tableOid,
												   fc_rte->insertedCols);
			fc_updatedCols = fc_fixup_inherited_columns(fc_rte->relid, fc_tableOid,
												  fc_rte->updatedCols);

			/*
			 * 检查单独表的权限
			 */
			if (!fc_check_relation_privileges(fc_tableOid,
										   fc_selectedCols,
										   fc_insertedCols,
										   fc_updatedCols,
										   fc_required, fc_abort_on_violation))
				return false;
		}
		list_free(fc_tableIds);
	}
	return true;
}
